using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;

namespace ODX.Core
{

#if ! PocketPC
    [Serializable]
#endif
    internal class Polymorpher : ICloneable
    {
        private Hashtable directInheritors = new Hashtable();
        private Dictionary<string, List<Type>> typesByTable = new Dictionary<string, List<Type>>();
        private Hashtable typesByTypeDef = new Hashtable();
        private Dictionary<Type, List<string>> tableAttributesStorage = new Dictionary<Type, List<String>>();
        private Dictionary<string, bool> versionedTables = new Dictionary<string, bool>();

        internal string[] GetTables()
        {
            return new List<string>(typesByTable.Keys).ToArray();
        }

        internal Type[] GetTypes()
        {
            return new List<Type>(tableAttributesStorage.Keys).ToArray();
        }

        private Type CheckChildren(Type type, DataRow row)
        {
            foreach (Type inheritor in GetDirectInheritors(type))
            {
                Type t = FindType(inheritor, row);
                if (t != null)
                    return t;
            }
            return null;
        }

        private Type[] GetDirectInheritors(Type type)
        {
            ArrayList al = (ArrayList)directInheritors[type];
            if ( al == null ) return new Type[0];
            return (Type[])al.ToArray(typeof(Type));
        }

        private Type FindType(Type rootType, DataRow row)
        {
            IEntityFactory factory = GetFactory(rootType);
            if (factory != null )
            {
                Type type = factory.DefineType(row);
                if (type != null )
                {
                    if (type.IsSubclassOf(rootType))
                    {
                        string ID = row["ID"].ToString();
                        DataRow subRow = Session.GetSession(row).FindRecord(GetTypeTable(type), ID);
                        return CheckChildren(type, subRow);
                    }
                    else
                        return type;
                }
            }

            return CheckChildren(rootType, row);
        }

        internal string GetTypeTable(Type type)
        {
            string[] tables = GetTypeTables(type);
            return tables.Length > 0 ? tables[tables.Length - 1] : null;
        }

        internal Type DefineType(DataRow row)
        {
            string tn = row.Table.TableName;
            Type rootType = GetRootTypeForTable(tn);
            Type[] di = GetDirectInheritors(rootType);

            // ----------------------------------------------
            // If "row" is a record from a table under a class which has no subclasses - we simply create class.
            // So programmer may avoid using TypeDefAttribure if he doesn't use inheitance.

            if (di.Length == 0)
                return rootType;

            // ----------------------------------------------

            Type type = FindType(rootType, row);
            if (type == null)
                type = rootType;


            if (type.IsAbstract)
                return SpecifyType(type);

            return type;
        }

        internal Type GetRootTypeForTable(string tn)
        {
            List<Type> tableTypes;
            typesByTable.TryGetValue(tn, out tableTypes);
            if (tableTypes == null || tableTypes.Count == 0)
                return null;
            Type type = tableTypes[0];

            while (tableTypes.Contains(type.BaseType))
                type = type.BaseType;

            return type;
        }

        private static IEntityFactory GetFactory(Type type)
        {
            TypeDefAttribute[] attrs = (TypeDefAttribute[])type.GetCustomAttributes(typeof(TypeDefAttribute), false);
            if (attrs.Length > 0)
                return attrs[0].Factory;
            return null;
        }

        internal void RegisterType(Type type)
        {
            if ( GetTypeTable(type.BaseType) != null )
            {
                ArrayList al = (ArrayList)directInheritors[type.BaseType];
                if (al == null)
                {
                    al = new ArrayList();
                    directInheritors[type.BaseType] = al;
                }
                if ( !al.Contains(type))
                    al.Add(type);
            }
            if (GetTypeTable(type) != null)
            {
                string tableName = GetTypeTable(type);
                List<Type> al;
                typesByTable.TryGetValue(tableName, out al);
                if (al == null)
                {
                    al = new List<Type>();
                    typesByTable[tableName] = al;
                }
                if (!al.Contains(type))
                    al.Add(type);
            }

            if (GetTypeTable(type) != null)
            {
                string typeDef = GetTypeDef(type);
                if (typeDef != null && typeDef.Length != 0)
                {
                    ArrayList al = (ArrayList)typesByTypeDef[typeDef];
                    if (al == null)
                    {
                        al = new ArrayList();
                        typesByTypeDef[typeDef] = al;
                    }
                    if (!al.Contains(type))
                        al.Add(type);
                }
            }
        }

        internal static string GetTypeDef(Type type)
        {
            TypeDefAttribute[] attrs = (TypeDefAttribute[])type.GetCustomAttributes(typeof(TypeDefAttribute), false);
            if (attrs.Length > 0)
                return attrs[0].Key;
            return null;
        }

        internal Type[] GetTypesForTypeDef(string typeDef)
        {
            ArrayList al = (ArrayList)typesByTypeDef[typeDef];
            if (al == null) return new Type[0];
            return (Type[])al.ToArray(typeof(Type));
        }

        // Creates array of attributes ordered from base to inheritor.
        internal string[] GetTypeTables(Type type)
        {
            List<string> al;
            tableAttributesStorage.TryGetValue(type, out al);
            if (al == null)
            {
                al = new List<string>();
                Type cType = type;
                while (cType.IsSubclassOf(typeof(Entity)))
                {
                    TableAttribute[] attrs = (TableAttribute[])cType.GetCustomAttributes(typeof(TableAttribute), false);
                    if (attrs.Length > 0)
                    {
                        string tableName = attrs[0].Name ?? cType.Name;
                        if ( !al.Contains(tableName) )
                            al.Insert(0, tableName);
                        versionedTables[tableName] = attrs[0].Versioned;
                    }
                    cType = cType.BaseType;
                }

                tableAttributesStorage[type] = al;
            }
            return al.ToArray();
        }

        internal bool DoesTableVersioned(string tableName)
        {
            bool ver;
            if (!versionedTables.TryGetValue(tableName, out ver))
                ver = false;
            return ver;
        }

        internal Type SpecifyType(Type type)
        {
            if ( ! type.IsAbstract )
                return type;

            foreach ( Type t in GetDirectInheritors(type) )
                if ( ! t.IsAbstract )
                    return t;

            throw new OdxException("Can't specify abstract type!!!");
        }

        object ICloneable.Clone()
        {
            return Clone();
        }

        public Polymorpher Clone()
        {
            Polymorpher pm = new Polymorpher();
            pm.directInheritors = new Hashtable(directInheritors);
            pm.typesByTable = new Dictionary<string, List<Type>>(typesByTable);
            pm.typesByTypeDef = new Hashtable(typesByTypeDef);
            pm.tableAttributesStorage = new Dictionary<Type, List<String>>(tableAttributesStorage);
            pm.versionedTables = new Dictionary<string, bool>(versionedTables);

            return pm;
        }
    }
}
